package main
//命令行参数解析、读取配置文件、设置logger
import (
	"context"
	"os"
	"os/signal"
	"path/filepath"
	"prome-shard/common"
	"prome-shard/config"
	"prome-shard/service"
	"prome-shard/target"
	cl "prome-shard/watch"
	"strings"
	"syscall"
	"time"

	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/oklog/run"
	"github.com/prometheus/common/promlog"
	promlogflag "github.com/prometheus/common/promlog/flag"
	"github.com/prometheus/common/version"
	"gopkg.in/alecthomas/kingpin.v2"
)

func main() {

	var (
		// 命令行参数
		app = kingpin.New(filepath.Base(os.Args[0]), "The prome-shard")
		// 指定配置文件
		configFile = app.Flag("config.file", "prome-shard configuration file path.").Default("prome-shard.yml").String()
	)
	promlogConfig := promlog.Config{}
	//
	app.Version(version.Print("prome-shard"))
	app.HelpFlag.Short('h')
	promlogflag.AddFlags(app, &promlogConfig)
	kingpin.MustParse(app.Parse(os.Args[1:]))

	// 设置logger
	var logger log.Logger
	logger = func(config *promlog.Config) log.Logger {
		var (
			l  log.Logger
			le level.Option
		)
		if config.Format.String() == "logfmt" {
			l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
		} else {
			l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
		}

		switch config.Level.String() {
		case "debug":
			le = level.AllowDebug()
		case "info":
			le = level.AllowInfo()
		case "warn":
			le = level.AllowWarn()
		case "error":
			le = level.AllowError()
		}
		l = level.NewFilter(l, le)
		l = log.With(l, "ts", log.TimestampFormat(
			func() time.Time { return time.Now().Local() },
			"2006-01-02T15:04:05.000Z07:00",
		), "caller", log.DefaultCaller)
		return l
	}(&promlogConfig)
	level.Debug(logger).Log("msg", "using_config_file", "filepath", *configFile)
	// 读取到配置文件
	sConfig, err := config.LoadFile(*configFile)

	// 初始化consul的client
	client, err := cl.NewConsulClient(sConfig.ConsulServer.Addr, logger)

	if err != nil || client == nil {
		level.Error(logger).Log("msg", "NewConsulClient Error, exiting ...", "error", err)
		return
	}

	// 总的context，ctxAll用来做goroutine间的协调，
	ctxAll, cancelAll := context.WithCancel(context.Background())
	srvNameChanMap := make(map[string]chan<- []string)
	//遍历配置中的分片服务，进行注册
	for _, i := range sConfig.ShardService {
		// 进行配置检查的工作
		i := i
		if !strings.HasPrefix(i.Name, common.ScrapePromeJobPrefix) {
			level.Warn(logger).Log("msg", "ShardService.Name.invalid", "name", i.Name)
			continue
		}
		// 加载对应类别名称的target 目前配置文件就两个
		//     scrape_prometheus_node_exporter
		//     scrape_prometheus_get_alert
		_, loaded := target.AvaiableGetTargetFuncs[i.Name]
		if !loaded {
			level.Warn(logger).Log("msg", "ShardService.Name.getTargetfunc.not.inplement", "name", i.Name)
			continue
		}

		// 先获取service对应的nodes，用返回健康的node做哈希环注册，避免宕掉节点的发送
		aliveNodes, _ := client.GetServiceNodes(i.Name)
		if len(aliveNodes) > 0 {
			i.Nodes = aliveNodes
		} else {
			// 注册配置文件prometheus服务 一个名称对应多个节点
			for _, n := range i.Nodes {
				n := n
				iport := strings.Split(n, ":")
				if len(iport) == 2 {
					n = iport[0]
				}
				err := client.ServiceRegister(i.Name, n, i.Port)
				if err != nil {
					level.Error(logger).Log("msg", "client.ServiceRegister.error", "error", err, "srvName", i.Name, "host", n, "port", i.Port)
					return
				}
			}
		}
		// 上面已经注册完毕 创建新的ShardService 并且将对应的prometheus的节点放入一致性hash环
		// 每一类的配置放入自己的一致性hash环,我们这有两个服务,所以有两个一致性hash环
		shardService := service.NewShardService(i, ctxAll, logger)

		// 初始化consulwatch
		srvNameChanMap[i.Name] = shardService.NodeUpdateChan
		// 启动正常的分发任务
		go shardService.RunDispatch()
	}

	var g run.Group
	{
		// Termination handler.
		term := make(chan os.Signal, 1)
		signal.Notify(term, os.Interrupt, syscall.SIGTERM)
		cancel := make(chan struct{})
		g.Add(

			func() error {
				select {
				case <-term:
					level.Warn(logger).Log("msg", "Received SIGTERM, exiting gracefully...")
					cancelAll()
					return nil
					//TODO clean work here
				case <-cancel:
					level.Warn(logger).Log("msg", "server finally exit...")
					return nil
				}
			},
			func(err error) {
				close(cancel)

			},
		)
	}
	{
		// WatchService   manager.
		g.Add(func() error {
			err := client.RunRefreshServiceNode(ctxAll, srvNameChanMap)
			if err != nil {
				level.Error(logger).Log("msg", "watchService_error", "error", err)
			}
			return err
		}, func(err error) {
			cancelAll()
		})
	}
	g.Run()

}
